﻿using System;
using System.Collections.Generic;
using System.Text;
using IPlayer;
using System.Runtime.InteropServices;
using System.Threading;
using System.Diagnostics;
using System.IO;
using System.Drawing;

namespace PlayerSDK
{
    public class PlayerSDKHC : IPlayerSDK
    {

        struct DecodeBuffer
        {
            public readonly IntPtr Buffer;
            public int Width;
            public int Height;
            public DecodeBuffer(IntPtr buffer, int width, int height)
            {
                this.Buffer = buffer;
                this.Width = width;
                this.Height = height;
            }
        }

        #region 私有变量
        private int width;
        private int height;
        IntPtr handle = IntPtr.Zero;                  //录像显示的窗体句柄

        private int playerPort = -1;            //播放端口，范围0-500
        int playSpeed = 0;              //播放速度


        long sumH264DecodeSpan;         //h264解码时间总和
        long countH264DecodeFrame = 1;  //h264解码帧数量
        long realH264DecodeSpan;        //实时帧解码时间间隔
        long sumDecodeSpan;         //h264解码时间总和
        long countDecodeFrame = 1;  //h264解码帧数量
        long realDecodeSpan;            //外部解码时间间隔
        Stopwatch watchFrameDecode = new Stopwatch();//解码时间测量器
        int workerThreads, completionPortThreads;

        #endregion

        #region 公有属性
        /// <summary>
        /// H264平均解码时间
        /// </summary>
        public long AverageH264DecodeSpan { get { return Convert.ToInt64(sumH264DecodeSpan / countH264DecodeFrame); } }
        /// <summary>
        /// 实时解码时间
        /// </summary>
        public long RealH264DecodeSpan { get { return realH264DecodeSpan; } }
        /// <summary>
        /// 外部平均解码时间
        /// </summary>
        public long AverageDecodeSpan { get { return Convert.ToInt64(sumDecodeSpan / countDecodeFrame); } }
        /// <summary>
        /// 外部解码时间间隔
        /// </summary>
        public long RealDecodeSpan { get { return realDecodeSpan; } }
        /// <summary>
        /// 可用线程池数
        /// </summary>
        public int AvailableThreads { get { return workerThreads; } }
        #endregion

        #region 私有方法
        private void DecCallBack(int nPort, IntPtr pBuf, int nSize, ref PlayCtrl.FRAME_INFO pFrameInfo, int nReserved1, int nReserved2)
        {
            this.realH264DecodeSpan = this.watchFrameDecode.ElapsedMilliseconds;
            this.watchFrameDecode.Reset();
            this.watchFrameDecode.Start();
            this.sumH264DecodeSpan += this.realH264DecodeSpan;
            this.countH264DecodeFrame++;

            if (this.decodeCallback != null)
                ThreadPool.QueueUserWorkItem(new WaitCallback(DecodeCall), new DecodeBuffer(pBuf, pFrameInfo.nWidth, pFrameInfo.nHeight));

            ThreadPool.GetAvailableThreads(out workerThreads, out completionPortThreads);
        }

        private void DrawCallback(int nPort, IntPtr hDc, int nUser)
        {
            if (this.paintCallback != null)
                this.paintCallback(Graphics.FromHdc(hDc));
        }

        /// <summary>
        /// 外部解码线程函数
        /// </summary>
        /// <param name="state"></param>
        void DecodeCall(object state)
        {
            DecodeBuffer buffer = (DecodeBuffer)state;
            Stopwatch watchDecodeCall = Stopwatch.StartNew();
            this.decodeCallback(buffer.Buffer, buffer.Width, buffer.Height);
            this.realDecodeSpan = watchDecodeCall.ElapsedMilliseconds;
            this.sumDecodeSpan += this.realDecodeSpan;
            this.countDecodeFrame++;
        }


        #endregion

        #region 构造函数

        #endregion

        #region IPlayerSDK 成员
        PlayCtrl.DrawFun DrawCallbackHandle;
        PaintCallback paintCallback;
        public PaintCallback PaintCallback
        {
            set { paintCallback = value; }
        }

        PlayCtrl.DecCBFun DecCBCallbackHandle;
        DecodeCallback decodeCallback;
        public DecodeCallback DecodeCallback
        {
            set { this.decodeCallback = value; }
        }

        OpenStateEnum openState = OpenStateEnum.Close;
        public OpenStateEnum OpenState
        {
            get { return this.openState; }
        }

        PlayStateEnum playState = PlayStateEnum.Other;
        public PlayStateEnum PlayState
        {
            get { return this.playState; }
        }

        public int Width
        {
            get { return this.width; }
        }

        public int Height
        {
            get { return this.height; }
        }

        public IntPtr Handle
        {
            get { return this.handle; }
        }

        public int PlaySpeed { get { return this.playSpeed; } }

        //public void FileRefDone(int nPort, ushort nUser)
        //{
        //    ushort nSize = 0;
        //    PlayCtrl.PlayM4_GetRefValue(nPort, IntPtr.Zero, ref nSize);
        //    byte[] Datas = new byte[nSize];

        //    GCHandle hObject = GCHandle.Alloc(Datas, GCHandleType.Pinned);
        //    IntPtr pObject = hObject.AddrOfPinnedObject();

        //    PlayCtrl.PlayM4_GetRefValue(nPort, pObject, ref nSize);

        //    PlayCtrl.PlayM4_SetRefValue(nPort, pObject, nSize);

        //    if (hObject.IsAllocated) hObject.Free();
        //}

        public bool Open(string file, IntPtr handle)
        {
            FileInfo record = new FileInfo(file);

            if (!record.Exists) throw new ApplicationException("Record is not exist!|Open");

            if (this.openState != OpenStateEnum.Close) throw new ApplicationException("Record has Opened!|Open");

            //生成索引
            //PlayCtrl.PlayM4_SetFileRefCallBack(0, FileRefDone, 0);
            if (!PlayCtrl.PlayM4_GetPort(ref this.playerPort)) return false;
            if (playerPort < 0 || playerPort > 500) throw new ArgumentOutOfRangeException("Port out of range!|Open");

            if (this.handle == IntPtr.Zero && handle != IntPtr.Zero) this.handle = handle;
            if (this.handle != IntPtr.Zero)
                PlayCtrl.PlayM4_SetFileEndMsg(playerPort, handle, 0x1111);

            if (PlayCtrl.PlayM4_OpenFile(playerPort, file))
            {
                this.openState = OpenStateEnum.FilePlay;

                this.DrawCallbackHandle = new PlayCtrl.DrawFun(DrawCallback);

                //注册绘图回调函数
                PlayCtrl.PlayM4_RigisterDrawFun(this.playerPort, this.DrawCallbackHandle, 0);

                return true;
            }
            else return false;
        }

        public bool Play()
        {
            if (this.openState != OpenStateEnum.FilePlay)
                throw new ApplicationException("File is not opened!|Play");

            if ( PlayCtrl.PlayM4_Play(playerPort, this.handle))
            {
                this.playSpeed = 0;
                this.playState = PlayStateEnum.Play;

                PlayCtrl.PlayM4_GetPictureSize(this.playerPort, out this.width, out this.height);

                return true;
            }
            else
                return false;
        }

        public bool Stop()
        {
            if (this.openState != OpenStateEnum.FilePlay)
                throw new ApplicationException("File is not opened!|Stop");

            if (this.playState != PlayStateEnum.Stop
                && this.playState != PlayStateEnum.Other
                && PlayCtrl.PlayM4_Stop(playerPort))
            {
                this.playState = PlayStateEnum.Stop;
                return true;
            }
            else
                return false;
        }

        public bool Pause()
        {
            if (this.openState != OpenStateEnum.FilePlay)
                throw new ApplicationException("File is not opened!|Pause");

            if (PlayCtrl.PlayM4_Pause(playerPort, true))
            {
                this.playState = PlayStateEnum.Pause;
                return true;
            }
            else
                return false;
        }

        public bool Continue()
        {
            if (this.openState != OpenStateEnum.FilePlay)
                throw new ApplicationException("File is not opened!|Continue");

            if (PlayCtrl.PlayM4_Pause(playerPort, false))
            {
                this.playState = PlayStateEnum.Play;
                return true;
            }
            else
                return false;
        }

        public bool Fast()
        {
            if (this.openState != OpenStateEnum.FilePlay)
                throw new ApplicationException("File is not opened!|Fast");

            if (this.playSpeed < 4 && PlayCtrl.PlayM4_Fast(playerPort))
            {
                this.playSpeed++;
                this.playState = PlayStateEnum.Play;
                return true;
            }
            else
                return false;

        }

        public bool Slow()
        {
            if (this.openState != OpenStateEnum.FilePlay)
                throw new ApplicationException("File is not opened!|Slow");
            if (this.playSpeed > -4 && PlayCtrl.PlayM4_Slow(playerPort))
            {
                this.playSpeed--;
                this.playState = PlayStateEnum.Play;
                return true;
            }
            else
                return false;
        }

        public bool OneByOne()
        {
            if (this.openState != OpenStateEnum.FilePlay)
                throw new ApplicationException("File is not opened!|OneByOne");
            if (PlayCtrl.PlayM4_OneByOne(playerPort))
            {
                this.playSpeed = 0;
                this.playState = PlayStateEnum.Single;
                return true;
            }
            else
                return false;
        }

        public bool OneByOneBack()
        {
            throw new NotImplementedException();

            //return PlayCtrl.PlayM4_OneByOneBack(playerPort);
        }

        /// <summary>
        /// 获取文件总时长
        /// </summary>
        /// <returns>总时长，单位：秒</returns>
        public uint GetFileTime()
        {
            if (this.openState != OpenStateEnum.FilePlay)
                throw new ApplicationException("File is not opened!|GetFileTime");

            return Convert.ToUInt32(PlayCtrl.PlayM4_GetFileTime(this.playerPort));
        }

        /// <summary>
        /// 获取总帧数
        /// </summary>
        /// <returns></returns>
        public uint GetTotalFrames()
        {
            if (this.openState != OpenStateEnum.FilePlay)
                throw new ApplicationException("File is not opened!|GetTotalFrames");

            return Convert.ToUInt32(PlayCtrl.PlayM4_GetFileTotalFrames(this.playerPort));
        }

        /// <summary>
        /// 获取帧率
        /// </summary>
        /// <returns></returns>
        public uint GetFrameRate()
        {
            if (this.openState != OpenStateEnum.FilePlay)
                throw new ApplicationException("File is not opened!|GetFrameRate");

            return PlayCtrl.PlayM4_GetCurrentFrameRate(this.playerPort);
        }

        /// <summary>
        /// 设置文件播放指针的相对位置
        /// </summary>
        /// <param name="position">范围 0-100%</param>
        /// <returns></returns>
        public bool SetPlayPos(float position)
        {
            if (this.openState != OpenStateEnum.FilePlay)
                throw new ApplicationException("File is not opened!|SetPlayPos");

            if (position > 1 || position < 0)
                throw new ArgumentOutOfRangeException("Position must be between 0 and 1!SetPlayPos");

            return PlayCtrl.PlayM4_SetPlayPos(this.playerPort, position);
        }

        /// <summary>
        /// 根据时间设置文件播放位置
        /// </summary>
        /// <param name="time">单位：毫秒</param>
        /// <returns></returns>
        public bool SetPlayedTime(uint time)
        {
            if (this.openState != OpenStateEnum.FilePlay)
                throw new ApplicationException("File is not opened!|SetPlayedTime");

            return PlayCtrl.PlayM4_SetPlayedTimeEx(this.playerPort, Convert.ToInt32(time));
        }

        //该方法需要建议录像文件索引
        public bool SetFrameNumber(uint number)
        {
            throw new NotImplementedException();
            //PlayM4_SetCurrentFrameNum
        }

        /// <summary>
        /// 获得文件播放指针的相对位置
        /// </summary>
        /// <returns>播放进度，范围：0%-100%</returns>
        public float GetPlayPos()
        {
            if (this.openState != OpenStateEnum.FilePlay)
                throw new ApplicationException("File is not opened!|GetPlayPos");

            return PlayCtrl.PlayM4_GetPlayPos(this.playerPort);
        }

        /// <summary>
        /// 得到文件当前播放的时间
        /// </summary>
        /// <returns>单位：毫秒</returns>
        public uint GetPlayedTime()
        {
            if (this.openState != OpenStateEnum.FilePlay)
                throw new ApplicationException("File is not opened!|GetPlayedTime");

            return PlayCtrl.PlayM4_GetPlayedTimeEx(this.playerPort);
        }

        /// <summary>
        /// 得到已经解码的视频帧数
        /// </summary>
        /// <returns></returns>
        public uint GetFrameNumber()
        {
            if (this.openState != OpenStateEnum.FilePlay)
                throw new ApplicationException("File is not opened!|GetFrameNumber");

            return PlayCtrl.PlayM4_GetCurrentFrameNum(this.playerPort);
        }

        public bool Open(string file)
        {
            FileInfo record = new FileInfo(file);

            if (!record.Exists) throw new ApplicationException("Record is not exist!|OpenFileDecode");
            
            ushort headLength = 40;//PlayCtrl.PlayM4_GetFileHeadLength();

            if (record.Length < headLength) throw new ApplicationException("Head length error!|OpenFileDecode");

            byte[] headData = new byte[headLength];

            IntPtr headBuffer = Marshal.AllocHGlobal(headData.Length);

            FileStream recordStream = new FileStream(record.FullName, FileMode.Open, FileAccess.Read);

            recordStream.Read(headData, 0, Convert.ToInt32(headLength));

            Marshal.Copy(headData, 0, headBuffer, headLength);

            if (!PlayCtrl.PlayM4_GetPort(ref playerPort)) return false;

            if (!PlayCtrl.PlayM4_SetStreamOpenMode(playerPort, PlayCtrl.STREAME_FILE)) return false;

            if (!PlayCtrl.PlayM4_OpenStream(playerPort, headBuffer, Convert.ToUInt32(headData.Length), 600 * 1024)) return false;

            if (!PlayCtrl.PlayM4_SetDecCBStream(playerPort, 1)) return false;

            this.DecCBCallbackHandle = new PlayCtrl.DecCBFun(this.DecCallBack);
            if (!PlayCtrl.PlayM4_SetDecCallBack(playerPort, this.DecCBCallbackHandle)) return false;

            if (!PlayCtrl.PlayM4_Play(playerPort, IntPtr.Zero)) return false;

            this.openState = OpenStateEnum.FileDecode;

            Marshal.FreeHGlobal(headBuffer);
            
            if (this.decodeCallback != null)
                ThreadPool.QueueUserWorkItem(new WaitCallback(RecordCall), recordStream);

            return true;
        }

        void RecordCall(object state)
        {
            FileStream recordStream = state as FileStream;
            byte[] recordData = new byte[10 * 1024];

            IntPtr recordBuffer = Marshal.AllocHGlobal(recordData.Length);

            while (recordStream.Position < recordStream.Length)
            {
                recordStream.Read(recordData, 0, recordData.Length);
                Marshal.Copy(recordData, 0, recordBuffer, recordData.Length);
                PlayCtrl.PlayM4_InputData(playerPort, recordBuffer, Convert.ToUInt32(recordData.Length));
                Thread.Sleep(40);
            }

            recordStream.Close();

            Marshal.FreeHGlobal(recordBuffer);
        }

        private AutoResetEvent InputLock = new AutoResetEvent(true);
        public bool Open(IntPtr buffer, uint bufferSize)
        {
            if (this.openState == OpenStateEnum.Close)
            {
                InputLock.WaitOne();

                if (!PlayCtrl.PlayM4_GetPort(ref playerPort)) return false;

                if (!PlayCtrl.PlayM4_SetStreamOpenMode(playerPort, PlayCtrl.STREAME_REALTIME)) return false;

                if (!PlayCtrl.PlayM4_OpenStream(playerPort, buffer, bufferSize, 600 * 1024)) return false;

                if (!PlayCtrl.PlayM4_GetPictureSize(playerPort, out this.width, out this.height)) return false;

                if (!PlayCtrl.PlayM4_SetDecCBStream(playerPort, 1)) return false;

                this.DecCBCallbackHandle = new PlayCtrl.DecCBFun(this.DecCallBack);
                if (!PlayCtrl.PlayM4_SetDecCallBack(playerPort, this.DecCBCallbackHandle)) return false;

                if (!PlayCtrl.PlayM4_Play(playerPort, IntPtr.Zero)) return false;

                Marshal.FreeHGlobal(buffer);

                this.openState = OpenStateEnum.RealDecode;

                InputLock.Set();

                return true;
            }
            else if (this.openState == OpenStateEnum.RealDecode)
            {
                PlayCtrl.PlayM4_InputData(playerPort, buffer, bufferSize);

                Marshal.FreeHGlobal(buffer);
            }

            return false;
        }

        public bool Close()
        {
            if (this.playerPort >= 0)
            {
                switch (this.openState)
                {
                    case OpenStateEnum.FilePlay:
                        PlayCtrl.PlayM4_CloseFile(playerPort);
                        PlayCtrl.PlayM4_FreePort(this.playerPort);
                        this.handle = IntPtr.Zero;
                        this.playSpeed = 0;
                        this.playerPort = -1;
                        this.playState = PlayStateEnum.Other;
                        this.openState = OpenStateEnum.Close;
                        break;
                    case OpenStateEnum.FileDecode:
                        PlayCtrl.PlayM4_CloseStream(this.playerPort);
                        PlayCtrl.PlayM4_FreePort(this.playerPort);
                        break;
                    case OpenStateEnum.RealDecode:
                        break;
                    case OpenStateEnum.Close:
                    case OpenStateEnum.Other:
                    default:
                        break;
                }

                this.openState = OpenStateEnum.Close;
                this.playerPort = -1;
            }

            return true;
        }




        #endregion

    }
}
